/*
 * Copyright (c) 2007, 2008 fr3@K <freak@fsfoundry.org>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef MANKA__TMP_ARENA_HPP
#define MANKA__TMP_ARENA_HPP

#include <manka/nullptr.hpp>
#include <manka/arena_allocator.hpp>
#include <manka/default_arena.hpp>

#include <cstddef>
#include <cassert>
#include <limits>

namespace manka
{

#if !defined(MANKA_DOC)
    namespace bits
    {
        struct alignment_helper
        {
            char foo;
        };
        
        template <std::size_t Bytes>
        struct tmp_buffer
        {
        public:
            char* begin()
                {
                    return buffer;
                }
            char* end()
                {
                    return buffer + sizeof(buffer);
                }

        private:
            char buffer[Bytes];
        };
        template <>
        struct tmp_buffer<0>
        {
            char* begin()
                {
                    return nullptr;
                }

            char* end()
                {
                    return nullptr;
                }
        };
        
    } // namespace bits
#endif // !MANKA_DOC

    /** A memory arena optimized for allocations of localy used memory.
     *
     */
    template <
        std::size_t BytesToPreserve,
        typename LowerLayerArena = default_arena>
    class tmp_arena
    {
    private:
        /** Disable copying.
         *
         */ 
        tmp_arena(const tmp_arena&);

        /** Disable assignment.
         *
         */ 
        tmp_arena& operator=(const tmp_arena&);

    public:
        static const std::size_t bytes_preserved =
            BytesToPreserve > 1024 ? 1024 : BytesToPreserve;
        
        typedef LowerLayerArena lower_layer_t;
        
    public:
        tmp_arena()
            :   buffer_(),
                next_(buffer_.begin()),
                DEBUG_outstanding_local_allocation_count_(0),
                DEBUG_outstanding_oversized_allocation_count_(0)
            {}
        ~tmp_arena()
            {
                assert(DEBUG_outstanding_local_allocation_count_ == 0);
                assert(DEBUG_outstanding_oversized_allocation_count_ == 0);
            }

    public:
        void* allocate(std::size_t bytes)
            {
                return allocate_(bytes);
            }
        
        void deallocate(void* ptr, std::size_t bytes)
            {
                deallocate_(ptr, bytes);
            }
        
        std::size_t max_bytes() const
            {
                return std::numeric_limits<std::size_t>::max();
            }

        arena_allocator<tmp_arena, void>
        get_allocator()
            {
                return arena_allocator<tmp_arena, void>(*this);
            }
        
    private:
#if !defined(MANKA_DOC)
        lower_layer_t lower_layer_;
        bits::tmp_buffer<bytes_preserved> buffer_;
        char* next_;

        static const std::size_t alignment_bytes =
            sizeof(bits::alignment_helper);
        static const std::size_t alignment_mask = ~alignment_bytes;

    public:
        std::size_t DEBUG_outstanding_local_allocation_count_;
        std::size_t DEBUG_outstanding_oversized_allocation_count_;

    private:
        void* allocate_(std::size_t bytes)
            {
                if(buffer_.end() - next_ < bytes)
                {
                    void* ret = lower_layer_.allocate(bytes);
                    ++DEBUG_outstanding_oversized_allocation_count_;
                    return ret;
                }
                
                void* ret = next_;
                next_ += ((bytes & alignment_mask) +
                          ((bytes & (alignment_bytes - 1)) ?
                           alignment_bytes : 0));
                ++DEBUG_outstanding_local_allocation_count_;

                return ret;
            }
        
        void deallocate_(void* ptr, std::size_t bytes)
            {
                char* p = reinterpret_cast<char*>(ptr);
                if(p >= buffer_.begin() &&
                   p < buffer_.end())
                {
                    --DEBUG_outstanding_oversized_allocation_count_;

                    lower_layer_.deallocate(ptr, bytes);
                    return;
                }

                --DEBUG_outstanding_local_allocation_count_;
            }
#endif // !MANKA_DOC
    };

} // namespace manka


#endif // !MANKA__TMP_ARENA_HPP
